package com.softwareengineering.trafficapplicationv2;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;




/**
 * This file is part of the Traffic Simulation Application.
 * The Traffic Simulation Application is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * The Traffic Simulation Application is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with the Traffic Simulation Application.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * @file ActivityGame.java
 * @author David Black, Chris Nelson, and Garrett Stibbs, (c) December 03, 2012
 * @description ActivityGame handles level presentation and control,
 *              gameplay, and results transmission/storage
 * @note Contains custom SendScoreAsyncTask for asynchronously sending local
 *      results from user device to remote database
 * 
 */

public class ActivityGame extends Activity implements Constants, OnSeekBarChangeListener, OnClickListener {

	//	######################################
	//		Activity Game Member Variables
	//	######################################
	
	// game play variables
	private int choice_Scen;	// level code (corresponding to "Constants.java"
	private double density = 0.001 * DENS_INIT_INVKM;	// default road density
	private int tsleep_ms = SPEED_INIT;	// sleeping time per simulation step
	private double qIn;	// main in flow
	private double qRamp = QRMP_INIT2 / 3600.0; // ramp in flow (veh/s)
	private double p_factorRamp = 0.0; // ramp politeness factor
	private double deltaBRamp = DELTABRAMP_INIT; // ramp lane change factor
	private double v0_limit;	// speed limit
	private double perTr = FRAC_TRUCK_INIT; // truck fraction
	private double p_factor = 0.0; // politeness factor
	private double deltaB = 0.2; // changing threshold
	
	// scoring variables
	private Timer countdown;	// timer responsible for scheduling GameTimer
	private double timer = 30.00;	// time limit per game
	private boolean isRunning = false;	// game-in-progress boolean
	private boolean isSending = false;	// results transmission-in-progress boolean
	final DecimalFormat formatter = new DecimalFormat("00.00");	// formatter for display of time remaining

	//	drawing variables 
	private SimCanvas mSimCanvas;	// handle to game canvas
	private SimThread mSimThread;	// handle to game thread

	// view handles
	private RelativeLayout mContentView;	// handle to content view
	private LinearLayout mPoliteFactor;	// handle to politeness control "button"
	private LinearLayout mTrafficLight;	// handle to traffic light control "button"
	private Button mGreen;	// handle to game-in-progress indicator (green light)
	private Button mRed;	// handle to game-stopped indicator (red light)
	private SeekBar mSeekPoliteFactor;	// handle to SeekBar politeness control slider
	private SeekBar mSeekSpeedLimit;	// handle to SeekBar speed limit control slider
	private TextView mPoliteLabel;	// handle to politeness display label
	private TextView mSpeedLabel;	// handle to speed limit display label
	private TextView mScoreText;	// handle to score display label
	private TextView mTimerText;	// handle to timer display label
	private TextView mMessageText;	// handle to upload-progress display label
	private TextView mAlertText;	// handle to "Times Up!" display label
	
	private Context context = this;	// handle to ActivityGame context
	
	//	#################
	//		GameTimer
	//	#################
	
	// responsible for enforcing the game play time limit
	public class GameTimer extends TimerTask {

		@Override
		public void run() {
			while(timer > 0.00 && isRunning) {	// while time remaining and game-in-progress
				timer -= 0.05;	// decrement time remaining
				if (timer < 0.00) timer = 0.00;	// do not allow negative zero
				runOnUiThread(new Runnable() {	// runOnUiThread when touching UI elements
					public void run() {
						mTimerText.setText("Time: " + formatter.format(timer));	// display time remaining
					}
				});
				try {
					Thread.sleep(50);	// sleep for .05 seconds
				}catch (Exception e) {};
			}
			if (isRunning) {	// if game play time limit successfully run down
				isRunning = false;	// set game-stopped
				runOnUiThread(onGameComplete);	// run onGameComplete handler
			}
		}
	}
	
	//	##########################
	//		SendScoreAsyncTask
	//	##########################

	// handles transmission of local results to remote database asynchronously
	private class SendScoreAsyncTask extends AsyncTask<ArrayList<NameValuePair>, Void, String> {

		@Override	// task to do asynchronously
		protected String doInBackground(ArrayList<NameValuePair>... params) {				
			String url = NetworkUtil.SEND_URL;	// URL of remote script to store local results remotely
			String response = "";	// initialize String to hold response
			HttpParams httpParams = new BasicHttpParams();	// create Object to hold Http parameters
			int timeoutConnection = 5000;	// specify connection timeout in milliseconds
			int timeoutSocket = 5000;	// specify socket timeout in milliseconds
			HttpConnectionParams.setConnectionTimeout(httpParams, timeoutConnection); // set connection timeout as specified
			HttpConnectionParams.setSoTimeout(httpParams, timeoutSocket);	// set socket timeout as specified
			DefaultHttpClient client = new DefaultHttpClient(httpParams);	// create HttpClient with desired Http parameters (timeouts)
			HttpPost httpPost = new HttpPost(url);	// use POST method to specified URL
			try {	// try
				httpPost.setEntity(new UrlEncodedFormEntity(params[0]));	// put local results in POST request
				HttpResponse execute = client.execute(httpPost);	// execute POST request from client
				InputStream content = execute.getEntity().getContent();	// create InputStream from content
				BufferedReader buffer = new BufferedReader(new InputStreamReader(content));	// create BufferedReader from InputStream
				String s = "";	// String to hold response line
				while ((s = buffer.readLine()) != null) {	// while line exists
					response += s;	// append line to response
				}
			} catch (Exception e) {	// fail
				e.printStackTrace();	// print stack trace for debugging
				return null;	// return null response
			}
			return response;	// return response on success
		}

		@Override	// task to do following execution
		protected void onPostExecute(String result) {
			if (result == null) {	// if execution failed
				didNotSendScore();	// call fail handler
			}
			
			int response = 0;	// initialize response to 0
			try {
				response = Integer.parseInt(result);	// attempt to parse result as integer
			}catch (NumberFormatException e) {}
			
			if (response != 6) didNotSendScore();	// if execution failed, call fail handler
			else didSendScore();	// if execution successful, call success handler
		}
	}
	
	//	######################
	//		onGameComplete
	//	######################

	// handles onGameComplete event
	private Runnable onGameComplete = new Runnable() {
		@Override
		public void run() {
			isSending = true;	// set transmission-in-progress
			mAlertText.setVisibility(View.VISIBLE);	// display "Time's Up!" label
			mGreen.setEnabled(false);	// disable game-in-progress indicator (green light)
			mRed.setEnabled(true);	// enable game-stopped indicator (red light)
			mTrafficLight.setEnabled(false);	// disable traffic light control
			mSimThread.pause();	// pause drawing thread
			
			Score score = new Score(choice_Scen, p_factor, v0_limit, mSimThread.getScore());	// create new Score object with results
			
			DatabaseHandler db = new DatabaseHandler(context);	// create new DatabaseHelper
			db.addScore(score);	// add current result to yet-to-submit table (also updates high scores table as appropriate)
			db.close();	// close DatabaseHelper
			
			if (NetworkUtil.isNetworkAvailable(context)) {	// if network connection is available
				doSendResults();	// call transmit handler
			}else {	// if network connection unavailable
				doNotSendResults();	// call unable-to-transmit handler
			}
		}
	};
	
	//	###################################
	//		ActivityGame Member Methods
	//	###################################

	// handles transmission fail event
	private void didNotSendScore() {
		mMessageText.setText("Upload Failed!");	// set upload progress display
		isSending = false;	// disable transmission-in-progress
		mTrafficLight.setEnabled(true);	// enable traffic light control for replay
	}

	// handles transmission success event
	private void didSendScore() {
		DatabaseHandler db = new DatabaseHandler(this);	// create new DatabaseHandler object
		db.deleteScores();	// clear all rows from yet-to-submit table
		db.close();	// close DatabaseHandler
		mMessageText.setText("Upload Successful!");	// set upload progress display
		isSending = false;	// disable transmission-in-progress
		mTrafficLight.setEnabled(true);	// enable traffic light control for replay
	}

	// handles start game event
	private void doGreenLight() {
		doResetGame();	// reset game scoring/timing variables
		hideViews(new View[] {mAlertText, mMessageText, mSeekPoliteFactor, mSeekSpeedLimit});	// hide views

		// call appropriate thread initialization method based on level code (corresponding to "Constants.java")
		if (choice_Scen != ON_RAMP) mSimThread.newValues(choice_Scen, density, qIn, perTr, v0_limit, p_factor, deltaB, tsleep_ms);
		else mSimThread.newValues2(qIn, perTr, p_factor, deltaB, qRamp, p_factorRamp, deltaBRamp, tsleep_ms, v0_limit);

		mSimThread.doStart();	// start drawing thread

		countdown = new Timer();	// initialize countdown timer
		countdown.schedule(new GameTimer(), 0);	// schedule and start GameTimer
	}

	// handles unable-to-transmit event
	private void doNotSendResults() {
		setMessage("Unable to Upload Score...");	// set upload display message
		isSending = false;	// disable transmission-in-progress
		mTrafficLight.setEnabled(true);	// enable traffic light control for replay
	}

	// handles toggling of politeness factor controls
	private void doPoliteFactor() {
		hideViews(new View[] {mAlertText, mMessageText, mSeekSpeedLimit});	// hide views
		doResetGame();	// reset game scoring/timing variables
		mSeekPoliteFactor.setVisibility(mSeekPoliteFactor.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);	// toggle politeness controls
	}

	// handles stop game event
	private void doRedLight() {
		if (countdown != null) countdown.cancel();	// cancel timer if applicable
		mSimThread.pause();	// pause drawing thread
		doResetGame();	// reset game scoring/timing variables
	}

	// handles reseting of game scoring/timing variables
	private void doResetGame() {
		mScoreText.setText("Score: 0");	// display initial score
		mTimerText.setText("Time: 30.00");	// display initial time remaining
		timer = 30.00;	// reset time remaining
	}

	// handles transmission attempt event
	private void doSendResults() {
		setMessage("Uploading Score...");	// set upload display message
		SendScoreAsyncTask task = new SendScoreAsyncTask();	// create new SendScoreAsyncTask to transmit results asynchronously
		task.execute(doSetParams());	// get results to send and execute asynchronous task
	}

	// handles fetching and return of results from not-yet-submitted table and parcels appropriately for transmission
	private ArrayList<NameValuePair> doSetParams() {
		DatabaseHandler db = new DatabaseHandler(this);	// create new DatabaseHandler object
		String results = db.getSubmitScores();	// get JSONArray String of yet-to-submit results
		ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();	// create new ArrayList of NameValuePairs
		params.add(new BasicNameValuePair("results", results));	// put results to params
		return params;	// return params
	}

	// handles toggling of speed limit controls
	private void doSpeedLimit() {
		hideViews(new View[] {mAlertText, mMessageText, mSeekPoliteFactor});	// hide views
		doResetGame();	// reset game scoring/timing variables
		mSeekSpeedLimit.setVisibility(mSeekSpeedLimit.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);	// toggle speed limit control
	}

	// hides all views passed in argument
	private void hideViews(View[] views) {
		for (View view : views) {	// for every passed in view
			view.setVisibility(View.GONE);	// hide visibility
		}
	}

	// handles initialization of game variables on ActivityGame launch
	public void initializeGame() {
		Bundle config = getIntent().getBundleExtra("config");	// get configuration bundle from launching Intent
		choice_Scen = config.getInt("choice_Scen");	// get level code (corresponding to "Constants.java")

		if (config.getBoolean("fromLoad")) {	// if launching a pre-configuration
			p_factor = p_factorRamp = config.getDouble("p_factor");	// set politeness factor
			v0_limit = config.getDouble("v0_limit");	// set speed limit
		} else if (choice_Scen == LANE_CLOSURE) v0_limit = 80 / 3.6;	// else set default speed limit if LANE_CLOSURE level
		else v0_limit = 120 / 3.6;	// otherwise set default speed limit

		// set main in flow default based on level code
		switch (choice_Scen) {
		case ON_RAMP:
			qIn = Q_INIT2 / 3600.0;	// set ON_RAMP default main inflow
			break;
		case LANE_CLOSURE:
			qIn = Q_INIT3 / 3600.0;	// set LANE_CLOSURE default main inflow
			break;
		case UPHILL:
			qIn = Q_INIT4 / 3600.0;	// set UPHILL default main inflow
			break;
		case FOUR_WAY:
			qIn = Q_INIT5 / 3600.0; // set FOUR_WAY default main inflow
			break;
		case TRAFFIC_LIGHT:
			qIn = Q_INIT6 / 3600.0; // set TRAFFIC_LIGHT default main inflow
		default:
			qIn = Q_INIT7 / 3600.0;	// set default main inflow
		}

		//create new SimCanvas object to handle display and manage control of game
		mSimCanvas = new SimCanvas(this, mContentView, choice_Scen, density, qIn, perTr, p_factor, deltaB, p_factorRamp, deltaBRamp, tsleep_ms);
		mSimCanvas.setTextView(mScoreText);	// set handle to score display
		mSimThread = mSimCanvas.getThread();	// get handle to drawing thread

		// call appropriate thread initialization method based on level code (corresponding to "Constants.java")
		if (choice_Scen != ON_RAMP) mSimThread.newValues(choice_Scen, density, qIn, perTr, v0_limit, p_factor, deltaB, tsleep_ms);
		else mSimThread.newValues2(qIn, perTr, p_factor, deltaB, qRamp, p_factorRamp, deltaBRamp, tsleep_ms, v0_limit);
	}

	// handles initialization of game controls on ActivityGame launch
	public void initializeSeekBars() {
		mSeekPoliteFactor.setOnSeekBarChangeListener(this);	// register politeness factor control listener
		mSeekSpeedLimit.setOnSeekBarChangeListener(this);	// register speed limit control listener

		p_factor = p_factorRamp = mSimThread.getPolitenessFactor();	// get politeness factor
		v0_limit = mSimThread.getSpeedLimit();	// get speed limit

		double p_factor = this.p_factor;	// copy politeness factor to manipulatable variable
		DecimalFormat formatter = new DecimalFormat("0.00");	// specify desired display format
		String sPFactor = formatter.format(p_factor);	// get desired display String in appropriate format

		// perform operations to make politeness factor compatible with valid SeekBar range (0-100)
		p_factor += 1.0;
		p_factor /= 3.0;
		p_factor *= 100.0;

		mPoliteLabel.setText(sPFactor);	// set politeness factor display
		mSeekPoliteFactor.setProgress((int)p_factor);	// set politeness factor slider position

		double v0_limit = 3.6 * this.v0_limit / 10.0;	// copy speed limit to manipulatable variable
		mSpeedLabel.setText("" + (int)(10.0 * v0_limit));	// set speed limit display

		// perform operations to make speed limit compatible with valid SeekBar range (0-100)
		v0_limit *= 10.0;
		v0_limit -= 20.0;
		v0_limit /= 120.0;
		v0_limit *= 100.0;

		mSeekSpeedLimit.setProgress((int)v0_limit);	// set speed limit slider position
	}
	
	// handles setting and display of upload progress notification
	private void setMessage(String text) {
		mMessageText.setText(text);	// set text as desired
		mMessageText.setVisibility(View.VISIBLE);	// make visible
	}
	
	//	#################
	//		Listeners
	//	#################
	
	@Override	// handles onClick events
	public void onClick(View view) {
		switch(view.getId()) {	// direct according to View Id
		case R.id.layout_politeFactor:	// if politeness factor control clicked
			if (mRed.isEnabled() && mTrafficLight.isEnabled()) {	// if game-stopped and controls enabled
				doPoliteFactor();	// call politeness factor control handler
			}
			break;
		case R.id.layout_speedLimit:	// if speed limit control clicked
			if (mRed.isEnabled() && mTrafficLight.isEnabled()) {	// if game-stopped and controls enabled
				doSpeedLimit();	// call speed limit control handler
			}
			break;
		case R.id.layout_trafficLight:	// if traffic light control clicked
			mGreen.setEnabled(!mGreen.isEnabled());	// toggle game-in-progress indicator (green light)
			mRed.setEnabled(!mRed.isEnabled());	// toggle game-stopped indicator (red light)
			isRunning = !isRunning;	// toggle game-in-progress boolean
			if (mGreen.isEnabled()) {	// if game-in-progress
				doGreenLight();	// call game start handler
			}else {	// if game-stopped
				doRedLight();	// call game stopped handler
			}
			break;
		}

	}
	
	@Override	// handles onCreate event
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);	// call super
		setContentView(R.layout.activity_game);	// set content view to appropriate layout

		// get handles to all UI views
		mContentView = (RelativeLayout) findViewById(R.id.activity_game);
		mPoliteFactor = (LinearLayout) findViewById(R.id.layout_politeFactor);
		mTrafficLight = (LinearLayout) findViewById(R.id.layout_trafficLight);
		mGreen = (Button) findViewById(R.id.button_green);
		mRed = (Button) findViewById(R.id.button_red);
		mSeekPoliteFactor = (SeekBar) findViewById(R.id.seek_politeFactor);
		mSeekSpeedLimit = (SeekBar) findViewById(R.id.seek_speedLimit);
		mPoliteLabel = (TextView) findViewById(R.id.text_politeFactor);
		mSpeedLabel = (TextView) findViewById(R.id.text_speedLimit);
		mScoreText = (TextView) findViewById(R.id.text_score);
		mTimerText = (TextView) findViewById(R.id.text_timer);
		mMessageText = (TextView) findViewById(R.id.text_message);
		mAlertText = (TextView) findViewById(R.id.text_alert);

		initializeGame();	// initialize game variables
		initializeSeekBars();	// initialize game controls

		DisplayMetrics metrics = new DisplayMetrics();	// create DisplayMetrics object for holding display metrics
		getWindowManager().getDefaultDisplay().getMetrics(metrics);	// put metrics to object
		final int margin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, metrics);	// specify desired margin

		//size traffic light
		mPoliteFactor.postDelayed(new Runnable() {	// post to politeness factor "button" to ensure execution AFTER layout and measuring
			public void run() {
				LayoutParams tParams = mTrafficLight.getLayoutParams();	// get traffic light layout params
				LayoutParams rParams = mRed.getLayoutParams();	// get red light layout params
				LayoutParams gParams = mGreen.getLayoutParams();	// get green light layout params
				tParams.height = mPoliteFactor.getHeight();	// set traffic light height to politeness factor height for uniformity
				rParams.width = gParams.width = tParams.height / 2 - margin;	// set red and green light widths appropriately
				mTrafficLight.setLayoutParams(tParams);	// put params back in traffic light
				mRed.setLayoutParams(rParams);	// put params back in red light
				mGreen.setLayoutParams(gParams);	// put params back in green light
			}
		}, 0);	// no delay
	}
	
	@Override	// handles onKeyDown event
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		switch(keyCode) {	// direct according to keyCode
		case KeyEvent.KEYCODE_BACK:	// BACK button pressed
			if (!isSending) {	// only do if transmission NOT in progress
				isRunning = false;	// set game-stopped
				mSimThread.pause();	// pause drawing thread
				if (countdown != null) countdown.cancel();	// cancel game countdown timer if appropriate
				startActivity(new Intent(this, ActivityLevels.class));	// start ActivityLevels activity
				finish();	// finish this activity to minimize stack use
			}
			return true;	// absorb event
		}
		return super.onKeyDown(keyCode, event);	// return super
	}
	
	@Override	// handles onProgressChanged event
	public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
		if (!fromUser) return;	// ignore event if not from user

		switch(seekBar.getId()) {	// direct according to appropriate SeekBar
		case R.id.seek_politeFactor:	// if controlling politeness factor
			// convert SeekBar progress into desired units
			double p_factor = progress / 100.0;
			p_factor *= 3.0;
			p_factor -= 1.0;
			
			DecimalFormat formatter = new DecimalFormat("0.00");	// specify desired display format
			String sPFactor = formatter.format(p_factor);	// get String in desired display format
			mPoliteLabel.setText(sPFactor);	// display politeness factor
			this.p_factor = this.p_factorRamp = p_factor;	// set politeness factor
			break;
		case R.id.seek_speedLimit:	// if controlling speed limit
			// convert SeekBar progress into desired units
			double v0_limit = progress / 100.0;
			v0_limit *= 120.0;
			v0_limit += 20.0;
			v0_limit /= 10.0;
			
			mSpeedLabel.setText("" + (int)(10.0 * v0_limit));	// display speed limit
			this.v0_limit = 10.0 * v0_limit / 3.6;	// set speed limit
			break;
		}

	}
	
	@Override	// mandatory implementation for OnSeekBarChangedListener
	public void onStartTrackingTouch(SeekBar seekBar) {}

	@Override	// handles onStop event: resets game
	protected void onStop() {
		if (isRunning) doResetGame();	// reset game scoring/timing variables if game-in-progress
		isRunning = false;	// set game stopped
		mSimThread.pause();	// pause drawing thread
		mRed.setEnabled(true);	// enable game-stopped indicator (red light)
		mGreen.setEnabled(false);	// disable game-in-progress indicator (green light)
		if (countdown != null) countdown.cancel();	// cancel game countdown timer if appropriate
		super.onStop();	// call super
	}

	@Override	// mandatory implementation for OnSeekBarChangedListener
	public void onStopTrackingTouch(SeekBar seekBar) {}

}
